/*
 * 
 */
package com.bonshotels.DomainLayer.DomainControllers;

import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.lang.Integer;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.bonshotels.DomainLayer.CtrlDades.FactoriaCtrlDades;
import com.bonshotels.DomainLayer.CtrlDades.ICtrlHotel;
import com.bonshotels.DomainLayer.CtrlDades.ICtrlClient;
import com.bonshotels.DomainLayer.CtrlDades.ICtrlHabitacio;
import com.bonshotels.DomainLayer.CtrlDades.IFactoriaCtrlDades;
import com.bonshotels.DomainLayer.DomainModel.Hotel;
import com.bonshotels.DomainLayer.DomainModel.Client;
import com.bonshotels.DomainLayer.DomainModel.Habitacio;
import com.bonshotels.DomainLayer.DomainModel.Reserva;
import com.bonshotels.DomainLayer.Factories.FactoriaObjectiuServeiAutoritzacioCredit;
import com.bonshotels.DomainLayer.ServeiAutoritzacioCredit.IObjectiuServeiAutoritzacioCredit;
import com.bonshotels.Excepcions.ClientNoExisteix;
import com.bonshotels.Excepcions.HabitacioNoExisteix;
import com.bonshotels.Excepcions.HotelNoExisteix;
import com.bonshotels.Excepcions.HotelsNoDisp;
import com.bonshotels.Excepcions.NoHiHaPoblacions;
import com.bonshotels.Excepcions.PoblacioNoExisteix;
import com.bonshotels.Excepcions.ServeiNoDisponible;

/**
 * Classe CUReservarHabitacio.
 * 
 * Aquesta classe implementa el patro Controlador en la seva variant Cas d'Us.
 * Es pot observar que els seus metodes son publics per tal de que es puguin
 * accedir des de fora del paquet, ja que caldra que siguin accedits des del
 * paquet PresentationLayer. Com que java no te tuples hem implementat les 
 * tuples del disseny amb un Map (Diccionari) on la clau es de tipus String, i es
 * correspon amb el nom de la variable de la tupla, i el valor es de tipus Object i
 * per tant ens permet posar-hi qualsevol cosa. Per obtenir les dades de la tupla
 * cal fer un get del valor passant-hi la clau (el nom de la variable) i fer un
 * downcasting (conversio de tipus baixant en la jerarquia de classes)
 * a la subclasse que ens interessa (i a la que sabem que pertany).
 * @author Marc Mauri Alloza 
 */
public class CUReservarHabitacio {
	
	/** Nom de la poblacio on es fa la reserva. */
	private String poblacio;
	
	/** Data d'inici de la reserva. */
	private Date dataInici;
	
	/** Data de fi de la reserva. */
	private Date dataFi;
	
	/** Nom de l'hotel on es fa la reserva. */
	private String hotel;
	
	/** Nom del tipus d'habitacio que es reserva. */
	private String tipusHabitacio;
	
	/** Numero d'habitacio que es reserva. */
	private int numHabitacio;
	
	/** Preu total de la reserva. */
	private float preuTotal;
	
	/** Dni del client que fa la reserva. */
	private String dni;
	
	/**
	 * Obte totes les poblacions existents al sistema. Aquesta funcionalitat es delega al
	 * controlador cas d'us CUBuscarHabitacio.
	 *
	 * @return poblacions existents al sistema
	 * @throws NoHiHaPoblacions 
	 */
	public Set<String> obtePoblacions() throws NoHiHaPoblacions {
		CUBuscarHabitacio cUBH = new CUBuscarHabitacio();
		Set<String> noms = cUBH.obtePoblacions();
		return noms;
	}
	
	/**
	 * Busca els hotels de la poblacio que tenen habitacions disponibles en el periode indicat i
	 * pel nombre d'ocupants. Per a cada hotel es mostra el seu nom, la seva descripcio i
	 * categoria, la mitjana de les avaluacions dels seus comentaris i els tipus, el nombre
	 * d'habitacions i el preu (depenent de la promocio que s'hagi aplicat) dels tipus
	 * d'habitacions que te disponibles. Aquesta funcionalitat es delega al controlador cas d'us
	 * CUBuscarHabitacio.
	 * Emmagatzema a la capa de domini la poblacio, data inici i data final.
	 * 
	 * @param pob Poblacio on volem buscar els hotels.
	 * @param dIni Data d'inici del periode on volem buscar els hotels.
	 * @param dFi Data de fi del periode on volem buscar els hotels.
	 * @param numOcup Numero d'ocupants que ocuparan l'habitacio.
	 * @return Un Set amb un Map amb el nom de l'hotel (key: "nomHotel"), la descripcio (key: "desc"),
	 * la categoria (key: "categoria"), la mitjana de les avaluacions dels comentaris (key: "avaluacio")
	 * i una llista dels tipus d' habitacio lliures (key: "habs") amb els seus noms
	 * (key: "tipusHab"), el nombre d'habitacions lliures (key: "numeroDisp") i el seu preu
	 * (key: "preu").
	 * @throws HotelsNoDisp 
	 * @throws PoblacioNoExisteix 
	 */
	public Set<Map<String,Object> > buscarHabitacio(String pob, Date dIni, Date dFi, int numOcup) throws HotelsNoDisp, PoblacioNoExisteix {
		CUBuscarHabitacio cUBH = new CUBuscarHabitacio();
		Set<Map<String, Object> > hotels = cUBH.buscarHabitacio(pob, dIni, dFi, numOcup);
		poblacio = pob;
		dataInici = dIni;
		dataFi = dFi;
		return hotels;
	}
	
	/**
	 * Obte les dades del tipus d'habitacio seleccionada: poblacio, nom de l'hotel, data inici i
	 * final, tipus d'habitacio i preu total.
	 * S'emmagatzema a la capa de domini l'hotel, tipus d'habitacio,
	 * numero d'habitacio (pot ser qualsevol de les que estiguin disponibles) i el preu total
	 * (depenent de la promocio que s'hagi aplicat). En aquesta operacio s'utilitza la
	 * FactoriaCtrlDades, la factoria implementa el patro Singleton, per aquest motiu no la
	 * instanciem (no podriem encara que volguessim ja que el constructor es privat) sino
	 * que utilitzem la operacio de classe obteInstancia() per accedir a la seva unica instancia.
	 * La FactoriaCtrlDades es una classe que implementa el patro ConcreteFactory
	 * i retorna una classe
	 * (definida al paquet DataLayer) que implementa la interficie
	 * ICtrl"NomDeLaClasse" (definida al paquet DomainLayer), d'aquesta manera aconseguim que la
	 * capa de dades depengui de la de domini i no al reves i trenquem una dependencia circular
	 * entre paquets que podria apareixer si DomainLayer depen de DataLayer per ontenir dades de
	 * capa de dades pero DataLayer depen de DomainLayer per construir els objectes de DataLayer 
	 * a partir de les dades que ha obtingut de la BD, i retornar-los a DataLayer. 
	 * 
	 *
	 * @param hotel Nom de l'hotel on esta l'habitacio que s'ha seleccionat.
	 * @param tipusHab El nom del tipus d'habitacio de l'habitacio que s'ha seleccionat.
	 * @return Les dades del tipus d'habitacio seleccionada: poblacio (key: "pob"), nom de l'hotel
	 * (key: "hotel"), data inici (key: "dIni") i final (key: "dFi"), tipus d'habitacio (key. "tipusHab")
	 * i preu total (key: "preuTotal").
	 * @throws HotelNoExisteix 
	 */
	public Map<String, Object> selecionarHabitacio(String hotel, String tipusHab) throws HotelNoExisteix {
		IFactoriaCtrlDades fCD = FactoriaCtrlDades.obteInstancia();
		ICtrlHotel cH = fCD.obteCtrlHotel();
		Hotel hot = cH.obte(hotel);
		
		Map<String, Object> infoHab = hot.seleccionaHabitacio(tipusHab, dataInici, dataFi);
		this.hotel = hotel;
		tipusHabitacio = tipusHab;
		numHabitacio = ( (Integer) infoHab.get("nHab") ).intValue();
		preuTotal = ( (Float) infoHab.get("preu") ).floatValue();
		Map<String, Object> resumDades = new TreeMap<String, Object>();
		resumDades.put("pob", poblacio);
		resumDades.put("dIni", dataInici);
		resumDades.put("dFi", dataFi);
		resumDades.put("hotel", hotel);
		resumDades.put("tipusHab", tipusHabitacio);
		resumDades.put("preuTotal", preuTotal);
		return resumDades;
	}

	/**
	 * Obte les dades del tipus d'habitacio seleccionada i del client: poblacio, nom de l'hotel,
	 * data inici i final, tipus d'habitació, dni, nom, cognoms i email del client i preu total
	 * (segons temporada). S'emmagatzema a la capa de domini el dni del client. En aquesta
	 * operacio s'utilitza la IFactoriaCtrlDades, la factoria de darrera la interficie
	 * implementa el patro Singleton, per
	 * aquest motiu no la instanciem (no podriem encara que volguessim ja que el constructor
	 * es privat) sino que utilitzem la operacio de classe obteInstancia() per accedir a la seva
	 * unica instancia.
	 * La FactoriaCtrlDades es una classe que implementa el patro ConcreteFactory i
	 * retorna una classe
	 * (definida al paquet DataLayer) que implementa la interficie
	 * ICtrl"NomDeLaClasse" (definida al paquet DomainLayer), d'aquesta manera aconseguim que la
	 * capa de dades depengui de la de domini i no al reves i trenquem una dependencia circular
	 * entre paquets que podria apareixer si DomainLayer depen de DataLayer per ontenir dades de
	 * capa de dades pero DataLayer depen de DomainLayer per construir els objectes de DataLayer 
	 * a partir de les dades que ha obtingut de la BD, i retornar-los a DataLayer. 
	 *
	 * @param dni Dni del client que fa la reserva.
	 * @return Un Map amb les dades del tipus d'habitacio seleccionada i del client: poblacio (key: "pob"),
	 * nom de l'hotel (key: "hotel"), data inici (key: "dIni") i final (key: "dFi"), tipus d'habitacio
	 * (key: "tipusHab"), dni (key: "dni"), nom (key: "nomClient"), cognoms (key: "cognoms") i
	 * email del client (key: "email") i preu total (key: "preuTotal")
	 * (segons temporada).
	 * @throws ClientNoExisteix 
	 */
	public Map <String, Object> introduirDni(String dni) throws ClientNoExisteix {
		IFactoriaCtrlDades fCD = FactoriaCtrlDades.obteInstancia();
		ICtrlClient cC = fCD.obteCtrlClient();
		Client c = cC.obte(dni);
		
		Map<String, Object> dades = c.obteDades();
		this.dni = dni;
		Map<String, Object> habClient = new TreeMap<String, Object>();
		habClient.put("pob", poblacio);
		habClient.put("dIni", dataInici);
		habClient.put("dFi", dataFi);
		habClient.put("hotel", hotel);
		habClient.put("tipusHab", tipusHabitacio);
		habClient.put("dni", dni);
		habClient.put("nomClient", dades.get("nom") );
		habClient.put("cognoms", dades.get("cognoms") );
		habClient.put("email", dades.get("email") );
		habClient.put("preuTotal", preuTotal);
		return habClient;
	}
	
	/**
	 * El sistema crida a l'operacio autoritza(numTarj, dCad, preuTotal) del adaptador del servei
	 * d'autoritzacio de credit amb les dades de la targeta i el preu total de la reserva.
	 * Si el pagament s'ha produit amb exit, es crea una instancia de reserva i de les seves
	 * associacions amb el client i l'habitacio. En aquesta operacio s'utilitzen la
	 * FactoriaCtrlDades, i la FactoriaObjectiuServeiAutoritzacioCredit, les factories implementen
	 * el patro Singleton, per aquest motiu no les instanciem (no podriem encara que volguessim
	 * ja que el constructor es privat) sino que utilitzem la operacio de classe obteInstancia()
	 * per accedir a les eves respectives instancies uniques.
	 * La FactoriaCtrlDades es una classe que implementa el patro ConcreteFactory i retorna una classe
	 * (definida al paquet DataLayer) que implementa la interficie
	 * ICtrl"NomDeLaClasse" (definida al paquet DomainLayer), d'aquesta manera aconseguim que la
	 * capa de dades depengui de la de domini i no al reves (i poder canviar la implementacio de
	 * la capa d'acces a dades sense provocar canvis a domini) i trenquem una dependencia circular
	 * entre paquets que podria apareixer si DomainLayer depen de DataLayer per ontenir dades de
	 * capa de dades pero DataLayer depen de DomainLayer per construir els objectes de DataLayer 
	 * a partir de les dades que ha obtingut de la BD, i retornar-los a DataLayer. 
	 * La FactoriaObjectiuServiAutoritzacioCredit esta aplicant el patro ConcreteFactory i retorna
	 * classes que implementen la interficie IObjectiuServiAutoritzacioCredit, ho fa per poder canviar la implementacio
	 * dels adaptador (i per tant els serveis concret que es consumeix) sense que els canvis es propaguin per
	 * la resta de la capa de domini.
	 *
	 * @param numTarj Numero de tarjeta de credit del client.
	 * @param dCad Data de caducitat de la targeta de credit del client.
	 * @throws ServeiNoDisponible 
	 * @throws ClientNoExisteix 
	 * @throws HabitacioNoExisteix 
	 */
	public void pagament(String numTarj, Date dCad) throws ServeiNoDisponible, ClientNoExisteix, HabitacioNoExisteix {
		FactoriaObjectiuServeiAutoritzacioCredit fOSAC = FactoriaObjectiuServeiAutoritzacioCredit.obteInstancia();
		IObjectiuServeiAutoritzacioCredit aSAC = fOSAC.creaOSAC();
		aSAC.pagament(numTarj, dCad, preuTotal);
		IFactoriaCtrlDades fCD = FactoriaCtrlDades.obteInstancia();
		ICtrlHabitacio cH = fCD.obteCtrlHabitacio();
		Habitacio habitacio = cH.obte(numHabitacio, hotel);
		
		ICtrlClient cC = fCD.obteCtrlClient();
		Client c = cC.obte(dni);
		
		new Reserva(habitacio, c, dataInici, dataFi, preuTotal);		
	}
	
}
